/**
 * 
 */
package de.fsujena.chat_pi.server;

import java.io.*;
import java.net.*;
import java.util.*;
import de.fsujena.chat_pi.connection.Connector;
import de.fsujena.chat_pi.connection.RSAException;
import de.fsujena.chat_pi.message.Message;
import de.fsujena.chat_pi.message.SysMessage;
import de.fsujena.chat_pi.message.SysMessage.*;
import de.fsujena.chat_pi.message.TextMessage;

/**
 * The message listener handles the direct communication between the server and a
 * specific client. It authenticates the user at the server and receives messages
 * from the client.
 * 
 * @author Erik Hebestreit
 * @version 1
 */
public class MessageListener extends Thread {
	int version = 1;
	
	public String username = "";
	
	boolean isConnected = false;
	boolean loggedIn = false;
		
	Server serverObject;
	Socket client;
	
	List<TextMessage> msgArray=new ArrayList<TextMessage>();
		
	Connector connector;
	SysMessageHandler sysMsgHandler;

	/**
	 * The constructor of the message listener handles the authorization process.
	 * It expects a string input from the client containing a whitespace separated list
	 * of the username and the corresponding password. The password and the username
	 * are checked. If the authorization is successful a message put out
	 * 
	 * @param client
	 * @throws IOException if the initiation of a new connection to a client fails
	 * @throws RSAException if there are problems setting up the RSA encryption
	 */
	public MessageListener(Socket client, Server serverObject) throws IOException, RSAException {
		this.client = client;
		this.serverObject = serverObject;
		
		// connector is launched as server and RSA-mode set
		connector = new Connector(client,true,false);
		
		// connection is started
		isConnected=connector.init();

		// handler for system messages is started
		sysMsgHandler = new SysMessageHandler(this);
		
		// send a system message to the client if they are connected
		sendSysMessage(CATEGORY.CONNECTION,MESSAGE.CONNECTED);	
	}	
	
	/**
	 * send a list with all users, that are currently logged in
	 */
	public void sendUserList() {
		int i=serverObject.networkListener.getNumberLoggedIn();
		String[] userArray = new String[i];
		
		int num = 0;
		
		// TODO: check if users are connected and logged in or not
		for(int n=0; n<serverObject.networkListener.messageListener.size();n++){
			if (serverObject.networkListener.messageListener.get(n).loggedIn) {
				userArray[num] = serverObject.networkListener.messageListener.get(n).username;
				num++;
			}
		}
		
		sendSysMessage(CATEGORY.STATUS, MESSAGE.SEND_USER_LIST, null, userArray);
	}


	/**
	 * launch thread for receiving messages from the client
	 */
	public void run() {
		
		Object inputObject = null;
		SysMessage sysMessage;
		
		// only runs while client is connected
		while (isConnected) {
			// if client is not logged in, the server only waits for system messages (e.g. registration or login)
			while (!loggedIn) {
				inputObject = null; // reset input object
				
				// read stream for new incoming system message
				try {
					inputObject = connector.readGeneralObject();
				} catch (IOException e) {
					//e.printStackTrace();
					close(true);
					break;
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
				
				if (inputObject instanceof SysMessage) {
					sysMessage = (SysMessage) inputObject;
					
					// send message to message handler
					sysMsgHandler.handleSysMessage(sysMessage);
				} else if (inputObject instanceof TextMessage){
					// as no text message is expected, send an information, that client is not logged in
					sendSysMessage(CATEGORY.LOGIN,MESSAGE.NOT_LOGGED_IN);
				}
				
				// once login was successful, send a user list
				if (loggedIn) {
					sendUserList();
				}
			}
			
			// if logged in, wait for all types of messages
			while (loggedIn) {
				inputObject = null;
				
				try {
					inputObject = connector.readGeneralObject();
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				} catch (IOException e) {
					close(true);
				}
				
				/*
				 * the following could be implemented using polymorphism
				 * 
				 * if (inputObject instanceof Message)
				 *     inputObject.handle();
				 */
				if (inputObject instanceof TextMessage){
					TextMessage tempObj = (TextMessage) inputObject;
					
					// add receiving time to text message
					tempObj.setReceived(new Date());
					
					// show the text message
					serverObject.ui.postTextMessage(tempObj);
					
					// add it to the history
					msgArray.add(tempObj);
					
					// distribute it to all other clients
					distributeMsg(tempObj);
				} else if (inputObject instanceof SysMessage) {
					sysMessage = (SysMessage) inputObject;
					
					// handle system message
					sysMsgHandler.handleSysMessage(sysMessage);		
				} /*else if (inputObject instanceof String){
					String temp = (String) inputObject;
					
					// post the string
					if (temp.isEmpty() == false)
						serverObject.postTextMessage(this.username,temp);
				}*/
			}
		}
	}
	
	/**
	 * send a system message to the client
	 * 
	 * @param cat category of the system message
	 * @param msg message code of the system message
	 * @see SysMessage
	 */
	public void sendSysMessage(SysMessage.CATEGORY cat, SysMessage.MESSAGE msg) {
		SysMessage sysMsg = new SysMessage(cat, msg);
		sysMsg.setSent(new Date());
		sysMsg.setSenderUsername(username);
		try {
			connector.sendMessageObject(sysMsg);
		} catch (IOException e) {
			e.printStackTrace();
			serverObject.ui.postError("Error sending message.");
		}
	}
	
	/**
	 * send a system message to the client
	 * 
	 * @param cat category of the system message
	 * @param msg message code of the system message
	 * @param msgStr additional string for the system message, containing additional informations
	 * @see SysMessage
	 */
	public void sendSysMessage(SysMessage.CATEGORY cat, SysMessage.MESSAGE msg, String msgStr) {
		SysMessage sysMsg = new SysMessage(cat, msg, msgStr);
		sysMsg.setSent(new Date());
		sysMsg.setSenderUsername("SERVER");
		try {
			connector.sendMessageObject(sysMsg);
		} catch (IOException e) {
			e.printStackTrace();
			serverObject.ui.postError("Error sending message.");
		}
	}
	
	/**
	 * send a system message to the client
	 * 
	 * @param cat category of the system message
	 * @param msg message code of the system message
	 * @param msgStr additional string for the system message, containing additional informations
	 * @param msgObj additional object, should not be large -> use FileMessage in these cases
	 * @see SysMessage
	 */
	public void sendSysMessage(SysMessage.CATEGORY cat, SysMessage.MESSAGE msg, String msgStr, Object msgObj) {
		SysMessage sysMsg = new SysMessage(cat, msg, msgStr);
		sysMsg.setMessageObject(msgObj);
		sysMsg.setSent(new Date());
		sysMsg.setSenderUsername("SERVER");
		try {
			connector.sendMessageObject(sysMsg);
		} catch (IOException e) {
			e.printStackTrace();
			serverObject.ui.postError("Error sending message.");
		}
	}

	
	/**
	 * distributes messages to other clients
	 * 
	 * @param msg Message Object containing the message
	 */	
	public void distributeMsg(Message msg){
		int i=serverObject.networkListener.messageListener.size()-1;		// get number of clients connected to the server
		
		for(int n=0; n<=i;n++){
			if((serverObject.networkListener.messageListener.get(n).username.equals(this.username)==false)&&(serverObject.networkListener.messageListener.get(n).loggedIn==true)){
				try {
					serverObject.networkListener.messageListener.get(n).connector.sendMessageObject(msg);
				} catch (IOException e) {
					e.printStackTrace();
					serverObject.ui.postError(String.format("Error sending message to %s.", serverObject.networkListener.messageListener.get(n).username));
				}	
			}
		}
		
		return;
	}
	
	/**
	 * closes socket, disconnect user and free resources
	 * eventually has to be implemented in NetworkListener
	 * @param lost defines if the connection was lost or canceled on purpose
	 */
	public void close(boolean lost){
		
		if(isConnected){							//because this method could be called twice
			isConnected=false;
			loggedIn=false;
			SysMessage temp=new SysMessage(CATEGORY.CONNECTION,MESSAGE.DISCONNECTED,username);
			temp.setSent(new Date());
			temp.setSenderUsername("SERVER");
			distributeMsg(temp);					//tell the other clients this one is diconnected
			serverObject.networkListener.removeClient(username);
			if(!lost){
					serverObject.ui.postInfo(String.format("user "+this.username+" disconnected"));
			}else {
					serverObject.ui.postInfo(String.format("user "+this.username+" lost connection"));
			}
		}		
	}
	
	/**
	 * deletes username so client can login as an other user
	 */
	public void logout(){
		
		SysMessage temp=new SysMessage(CATEGORY.LOGIN,MESSAGE.LOGGED_OUT,username);
		temp.setSent(new Date());
		temp.setSenderUsername("SERVER");
		distributeMsg(temp);
		try {
			connector.sendMessageObject(temp);
		} catch (IOException e) {
			e.printStackTrace();
			serverObject.ui.postError("Error sending message.");
		}
		loggedIn=false;
		
	}
}
